package com.fast.alden.portal.service.impl;

import com.fast.alden.common.entity.dto.LoginParam;
import com.fast.alden.common.entity.dto.LoginResult;
import com.fast.alden.common.entity.dto.VerifyCodeEntity;
import com.fast.alden.common.exception.BizException;
import com.fast.alden.common.exception.BizExceptionEnum;
import com.fast.alden.common.service.RedisService;
import com.fast.alden.common.service.TokenService;
import com.fast.alden.common.util.IpUtils;
import com.fast.alden.common.util.ServletUtils;
import com.fast.alden.data.model.PortalUser;
import com.fast.alden.data.repo.PortalUserRepository;
import com.fast.alden.portal.entity.dto.RegisterParam;
import com.fast.alden.portal.entity.dto.ResetPasswordParam;
import com.fast.alden.portal.entity.vo.PortalOnlineUser;
import com.fast.alden.portal.security.model.PortalUserDetailsImpl;
import com.fast.alden.portal.service.AuthPortalService;
import com.fast.alden.portal.service.PortalUserPortalService;
import com.fast.alden.portal.util.Constants;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.base.Captcha;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Service
public class AuthPortalServiceImpl implements AuthPortalService {
    @Value("${fast-alden.secure.verify-code.enabled}")
    private boolean verifyCodeEnabled;
    @Value("${fast-alden.secure.verify-code.expiration}")
    private Long verifyCodeExpiration;
    @Value("${fast-alden.secure.jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${fast-alden.secure.jwt.tokenHead}")
    private String tokenHead;
    @Value("${fast-alden.secure.verify-code.use-redis}")
    private boolean storeVerifyCodeInRedis;
    @Resource
    private RedisService redisService;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private TokenService tokenService;
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private PortalUserPortalService userService;
    @Resource
    private ScheduledExecutorService scheduledExecutorService;
    @Resource
    private PortalUserRepository portalUserRepository;

    @Override
    public VerifyCodeEntity generateVerifyCode() throws IOException {
        // 创建验证码对象
        Captcha captcha = new ArithmeticCaptcha();
//        captcha.setLen(4);

        // 生成验证码编号
        String verifyCodeKey = UUID.randomUUID().toString();
        String verifyCode = captcha.text();

        // 获取验证码图片，构造响应结果
        VerifyCodeEntity verifyCodeEntity = new VerifyCodeEntity(verifyCodeKey, captcha.toBase64(), verifyCode);

        if (verifyCodeEnabled) {
            storeVerifyCode(verifyCodeKey, verifyCode);
        }

        return verifyCodeEntity;
    }

    @Override
    public void checkVerifyCode(String verifyCodeKey, String verifyCode) {
        // 禁用验证码校验时直接返回true
        if (!verifyCodeEnabled) {
            return;
        }
        if (storeVerifyCodeInRedis) {
            // 判断验证码是否过期
            if (redisService.getExpire(verifyCodeKey) < 0) {
                throw new BizException(BizExceptionEnum.VERIFY_CODE_IS_EXPIRED);
            }
        }

        String expect = getVerifyCode(verifyCodeKey);

        if (!StringUtils.hasText(expect) || !StringUtils.hasText(verifyCode) || !verifyCode.equalsIgnoreCase(expect)) {
            throw new BizException(BizExceptionEnum.VERIFY_CODE_IS_WRONG);
        }
    }

    @Override
    @Transactional
    public LoginResult login(LoginParam param) {
        checkVerifyCode(param.getVerifyCodeKey(), param.getVerifyCode());

        PortalUser user = userService.findByAccount(param.getUsername());
        if (user == null) {
            throw new BizException(BizExceptionEnum.USER_DOSE_NOT_EXIST);
        }
        if (!passwordEncoder.matches(param.getPassword(), user.getPassword())) {
            throw new BizException(BizExceptionEnum.USER_PASSWORD_IS_WRONG);
        }

        // 用户验证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(param.getUsername(), param.getPassword());
        // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        // 获取认证成功后的对象
        PortalUserDetailsImpl userDetails = (PortalUserDetailsImpl) authentication.getPrincipal();
        String token = tokenService.generateToken(userDetails);
        // redis记录登录信息
        PortalOnlineUser onlineUser = PortalOnlineUser.load(userDetails.getUser());
        onlineUser.setToken(token);
        onlineUser.setUserId(userDetails.getUser().getId());
        onlineUser.setLoginTime(LocalDateTime.now());
        onlineUser.setIp(IpUtils.getIpAddr());
        redisService.set(Constants.CACHE_KEY_PREFIX_PORTAL_USER_ONLINE + user.getUsername() + ":" + token, onlineUser);

        LoginResult result = new LoginResult();
        result.setToken(token);

        // 更新最后登录信息
        user.setLastLoginIp(IpUtils.getIpAddr());
        user.setLastLoginTime(LocalDateTime.now());
        userService.updateLoginInfo(user);

        return result;
    }

    @Override
    public void logout() {
        HttpServletRequest request = ServletUtils.getRequest();

        // redis中删除用户登录信息
        String authHeader = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            // "Bearer "后的部分
            String authToken = authHeader.substring(this.tokenHead.length());
            String username = tokenService.getUserNameFromToken(authToken);
            // 从redis中删除token
            String key = Constants.CACHE_KEY_PREFIX_PORTAL_USER_ONLINE + username + ":" + authToken;
            redisService.del(key);
        }
        // 清空spring security上下文
        SecurityContextHolder.clearContext();
    }

    @Override
    public void register(RegisterParam param) {
        checkVerifyCode(param.getVerifyCodeKey(), param.getVerifyCode());

        PortalUser user = RegisterParam.convert(param);

        userService.create(user);
    }

    @Override
    public boolean checkUsernameBeforeResetPassword(String username) {
        return userService.existUsername(username);
    }

    @Override
    public boolean checkMainInfo(ResetPasswordParam param) {
        PortalUser user = userService.findByAccount(param.getUsername());
        if (user == null) {
            return false;
        }
        if (user.getRealName().equals(param.getRealName()) && user.getPhone().equals(param.getPhone())) {
            return true;
        }
        return false;
    }

    @Override
    public void resetPassword(ResetPasswordParam param) {
        PortalUser user = userService.findByAccount(param.getUsername());
        if (user == null) {
            throw new BizException(BizExceptionEnum.USER_DOSE_NOT_EXIST);
        }
        user.setPassword(passwordEncoder.encode(param.getNewPwd()));
        portalUserRepository.save(user);
    }

    @Override
    public boolean checkUsername(String username) {
        return !userService.existUsername(username);
    }

    @Override
    public boolean checkPhone(String phone) {
        return !userService.existPhone(phone);
    }

    @Override
    public boolean checkEmail(String email) {
        return !userService.existEmail(email);
    }

    /**
     * 缓存验证码
     *
     * @param verifyCodeKey 验证码唯一标识
     * @param verifyCode    验证码值
     */
    private void storeVerifyCode(String verifyCodeKey, String verifyCode) {
        if (storeVerifyCodeInRedis) {
            // 存入Redis
            redisService.set(verifyCodeKey, verifyCode, verifyCodeExpiration);
            return;
        }
        // 存入session
        HttpSession session = ServletUtils.getRequest().getSession();
        session.setAttribute(verifyCodeKey, verifyCode);
        // 超时后删除验证码缓存
        scheduledExecutorService.schedule(() -> {
            session.removeAttribute(verifyCode);
        }, verifyCodeExpiration, TimeUnit.SECONDS);
    }

    /**
     * 读取验证码
     */
    private String getVerifyCode(String verifyCodeKey) {
        if (storeVerifyCodeInRedis) {
            // 从redis读取验证码
            String verifyCode = (String) redisService.get(verifyCodeKey);
            // 从redis中删除
            redisService.del(verifyCodeKey);

            return verifyCode;
        }
        HttpSession session = ServletUtils.getRequest().getSession();
        // 从session读取验证码
        String verifyCode = (String) session.getAttribute(verifyCodeKey);
        // 从session中删除
        session.removeAttribute(verifyCodeKey);

        return verifyCode;
    }
}
